/**
 * 参考 https://github.com/jspm/vite-plugin-jspm，调整为需要的功能
 */
import type { GeneratorOptions } from '@jspm/generator'
import type { Plugin } from 'vite'

import { Generator } from '@jspm/generator'
import { load } from 'cheerio'
import { minify } from 'html-minifier-terser'

const DEFAULT_PROVIDER = 'jspm.io'

type pluginOptions = GeneratorOptions & {
    debug?: boolean
    defaultProvider?: 'esm.sh' | 'jsdelivr' | 'jspm.io'
    importmap?: Array<{ name: string; range?: string }>
}

// async function getLatestVersionOfShims() {
//   const result = await fetch('https://ga.jspm.io/npm:es-module-shims');
//   const version = result.text();
//   return version;
// }

async function getShimsUrl(provide: string) {
    // const version = await getLatestVersionOfShims();
    const version = '1.10.0'

    const shimsSubpath = `dist/es-module-shims.js`
    const providerShimsMap: Record<string, string> = {
        'esm.sh': `https://esm.sh/es-module-shims@${version}/${shimsSubpath}`,
        // unpkg: `https://unpkg.com/es-module-shims@${version}/${shimsSubpath}`,
        jsdelivr: `https://cdn.jsdelivr.net/npm/es-module-shims@${version}/${shimsSubpath}`,

        // 下面两个CDN不稳定，暂时不用
        'jspm.io': `https://ga.jspm.io/npm:es-module-shims@${version}/${shimsSubpath}`
    }

    return providerShimsMap[provide] || providerShimsMap[DEFAULT_PROVIDER]
}

let generator: Generator

async function viteImportMapPlugin(pluginOptions?: pluginOptions): Promise<Plugin[]> {
    const { importmap } = pluginOptions || {}

    let isSSR = false
    let isBuild = false
    let installed = false
    let installError: Error | null = null

    const options: pluginOptions = Object.assign(
        {},
        {
            debug: false,
            defaultProvider: 'jspm.io',
            env: ['production', 'browser', 'module'],
            importmap: []
        },
        pluginOptions
    )

    generator = new Generator({
        ...options,
        baseUrl: process.cwd()
    })

    if (options?.debug) {
        ;(async () => {
            for await (const { message, type } of generator.logStream()) {
                console.log(`${type}: ${message}`)
            }
        })()
    }

    const imports = options.inputMap?.imports ?? {}
    const scopes = options.inputMap?.scopes ?? {}
    const firstLayerKeys = Object.keys(scopes)
    const inputMapScopes: string[] = []
    firstLayerKeys.forEach((key) => {
        inputMapScopes.push(...Object.keys(scopes[key] || {}))
    })
    const inputMapImports = Object.keys(imports)

    const allDepNames: string[] = [
        ...(importmap?.map((item) => item.name) || []),
        ...inputMapImports,
        ...inputMapScopes
    ]
    const depNames = new Set<string>(allDepNames)

    const installDeps = importmap?.map((item) => ({
        range: item.range,
        target: item.name
    }))

    return [
        {
            async config(_, { command, isSsrBuild }) {
                isBuild = command === 'build'
                isSSR = !!isSsrBuild
            },
            enforce: 'pre',
            name: 'importmap:external',
            resolveId(id) {
                if (isSSR || !isBuild) {
                    return null
                }

                if (!depNames.has(id)) {
                    return null
                }
                return { external: true, id }
            }
        },
        {
            enforce: 'post',
            name: 'importmap:install',
            async resolveId() {
                if (isSSR || !isBuild || installed) {
                    return null
                }
                try {
                    installed = true
                    await Promise.allSettled(
                        (installDeps || []).map((dep) => generator.install(dep))
                    )
                } catch (error: any) {
                    installError = error
                    installed = false
                }
                return null
            }
        },
        {
            buildEnd() {
                // 未生成importmap时，抛出错误，防止被turbo缓存
                if (!installed && !isSSR) {
                    installError && console.error(installError)
                    throw new Error('Importmap installation failed.')
                }
            },
            enforce: 'post',
            name: 'importmap:html',
            transformIndexHtml: {
                async handler(html) {
                    if (isSSR || !isBuild) {
                        return html
                    }

                    const importmapJson = generator.getMap()

                    if (!importmapJson) {
                        return html
                    }

                    const esModuleShimsSrc = await getShimsUrl(
                        options.defaultProvider || DEFAULT_PROVIDER
                    )

                    const resultHtml = await injectShimsToHtml(html, esModuleShimsSrc || '')
                    html = await minify(resultHtml || html, {
                        collapseWhitespace: true,
                        minifyCSS: true,
                        minifyJS: true,
                        removeComments: false
                    })

                    return {
                        html,
                        tags: [
                            {
                                attrs: {
                                    type: 'importmap'
                                },
                                injectTo: 'head-prepend',
                                tag: 'script',
                                children: `${JSON.stringify(importmapJson)}`
                            }
                        ]
                    }
                },
                order: 'post'
            }
        }
    ]
}

async function injectShimsToHtml(html: string, esModuleShimUrl: string) {
    const $ = load(html)

    const $script = $(`script[type='module']`)

    if (!$script) {
        return
    }

    const entry = $script.attr('src')

    $script.removeAttr('type')
    $script.removeAttr('crossorigin')
    $script.removeAttr('src')
    $script.html(`
if (!HTMLScriptElement.supports || !HTMLScriptElement.supports('importmap')) {
  self.importShim = function () {
      const promise = new Promise((resolve, reject) => {
          document.head.appendChild(
              Object.assign(document.createElement('script'), {
                  src: '${esModuleShimUrl}',
                  crossorigin: 'anonymous',
                  async: true,
                  onload() {
                      if (!importShim.$proxy) {
                          resolve(importShim);
                      } else {
                          reject(new Error('No globalThis.importShim found:' + esModuleShimUrl));
                      }
                  },
                  onerror(error) {
                      reject(error);
                  },
              }),
          );
      });
      importShim.$proxy = true;
      return promise.then((importShim) => importShim(...arguments));
  };
}

var modules = ['${entry}'];
typeof importShim === 'function'
  ? modules.forEach((moduleName) => importShim(moduleName))
  : modules.forEach((moduleName) => import(moduleName));
 `)
    $('body').after($script)
    $('head').remove(`script[type='module']`)
    return $.html()
}

export { viteImportMapPlugin }
